package util.collections;

import java.lang.ref.ReferenceQueue;
import java.lang.ref.SoftReference;
import java.util.HashMap;
import java.util.Map;

/**
 * Internally maintains a map which stores contents using soft
 * references instead of hard references.
 * 
 * Based on SoftHashMap implementation from
 * http://www.javaspecialists.eu/archive/Issue015.html 
 * @author Joel Rausch
 */
public class SoftCache {

	/** Internal hash map holding soft references to cached objects. */
	private final Map<String, SoftReference<Object>> cache;
	
	/** Internal queue of soft references to collected objects. */
	private final ReferenceQueue<Object> deadRefs;
	
	/** Hooks into object destruction to reclaim system resources. */
	private final CacheReaper reaper;
	
	/** Default constructor */
	public SoftCache() {
		cache = new HashMap<String, SoftReference<Object>>();
		deadRefs = new ReferenceQueue<Object>();
		reaper = null;
	}
	
	public SoftCache(CacheReaper reaper) {
		cache = new HashMap<String, SoftReference<Object>>();
		deadRefs = new ReferenceQueue<Object>();
		this.reaper = reaper;
	}
	
	/**
	 * Returns the object if it is contained in the cache, or null if
	 * it does not.
	 * @param name
	 * @return
	 */
	public Object get(String name) {
		// Fast fail condition
		if(name == null) {
			return null;
		}
		
		SoftReference<Object> ref = cache.get(name);
		if(ref != null) {
			Object obj = ref.get();
			if(obj != null) {
				return obj;
			} else {
				cache.remove(ref);
			}
		}
		return null;
	}
	
	/**
	 * Stores name value pairs with soft reference in map.
	 * @param name
	 * @param value
	 */
	public void put(String name, Object value) {
		// Fast fail condition
		if(name == null || value == null) {
			return;
		}
		
		SoftReferenceWrapper ref = new SoftReferenceWrapper(value, name, deadRefs);
		cache.put(name, ref);
	}
	
	/** Periodic function which maintains the cache. */
	public void maintain() {
		reapDead();
	}
	
	/**
	 * Wraps the SoftReference class so we can reap dead instances
	 * more efficiently from the cache.
	 * 
	 * @author Joel Rausch
	 */
	private class SoftReferenceWrapper extends SoftReference<Object> {

		/** Internal key mapping to the reference. */
		private final String key;
		
		/**
		 * Constructor taking additional key parameter which maps to the referent.
		 * @param referent
		 * @param key
		 * @param queue
		 */
		public SoftReferenceWrapper(Object referent, String key, ReferenceQueue<Object> queue) {
			super(referent, queue);
			this.key = key;
		}
		
		/** Returns the key mapping to the reference. */
		public String getKey() {
			return key;
		}
	}
	
	/** Cleans the cache of dead soft references. */
	protected void reapDead() {
		SoftReferenceWrapper ref;
		while((ref = (SoftReferenceWrapper) deadRefs.poll()) != null) {
			cache.remove(ref.getKey());
		}
	}
}
